﻿#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Utilities.Miscellaneous;
using System.Collections;

namespace Newtonsoft.Utilities.Collections
{
  public static class EnumerableExtensions
  {
    public static Position GetPosition<T>(this IEnumerable<T> collection, T item)
    {
      PositionEntry<T> itemEntry = (new PositionEnumerable<T>(collection))
        .Single(e => MiscellaneousUtils.Equals(e.Value, item));

      return itemEntry.Position;
    }

    public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
    {
      ForEach(enumerable, action, null);
    }

    /// <summary>
    /// Performs an action for each item in an enumerable, divided by a seperator action.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable">The enumerable.</param>
    /// <param name="action">The action.</param>
    /// <param name="seperatorAction">The seperator action.</param>
    public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action, Action<T> seperatorAction)
    {
      ValidationUtils.ArgumentNotNull(enumerable, "enumerable");
      ValidationUtils.ArgumentNotNull(action, "action");

      bool first = true;

      foreach (T item in enumerable)
      {
        if (first)
        {
          first = false;
        }
        else
        {
          if (seperatorAction != null)
            seperatorAction(item);
        }

        action(item);
      }
    }

    public static IEnumerable<T> Convert<T>(this IEnumerable enumerable)
    {
      return Convert<T>(enumerable, false);
    }

    public static IEnumerable<T> Convert<T>(this IEnumerable enumerable, bool defaultOnBadType)
    {
      ValidationUtils.ArgumentNotNull(enumerable, "enumerable");

      return new EnumerableWrapper<T>(enumerable, defaultOnBadType, true);
    }

    public static string ToString(this IEnumerable enumerable)
    {
      return ToString(enumerable, MiscellaneousUtils.ToString, ", ");
    }

    public static string ToString(this IEnumerable enumerable, Converter<object, string> converter)
    {
      return ToString(enumerable, converter, ", ");
    }

    public static string ToString(this IEnumerable enumerable, Converter<object, string> converter, string separator)
    {
      ValidationUtils.ArgumentNotNull(enumerable, "enumerable");

      return ToString<object>(Convert<object>(enumerable), converter, separator);
    }

    public static string ToString<T>(this IEnumerable<T> collection, Converter<T, string> converter)
    {
      return ToString<T>(Convert<T>(collection), converter, ", ");
    }

    public static string ToString<T>(this IEnumerable<T> enumerable, Converter<T, string> converter, string separator)
    {
      ValidationUtils.ArgumentNotNull(enumerable, "enumerable");
      ValidationUtils.ArgumentNotNull(converter, "converter");
      ValidationUtils.ArgumentNotNull(separator, "separator");

      List<T> list = new List<T>(enumerable);

      return ToString(enumerable.Select(i => converter(i)), separator);
    }

    public static string ToString(this IEnumerable<string> enumerable, string separator)
    {
      ValidationUtils.ArgumentNotNull(enumerable, "enumerable");
      ValidationUtils.ArgumentNotNull(separator, "separator");

      return string.Join(separator, enumerable.ToArray());
    }

    public static bool IsNullOrEmpty(this IEnumerable enumerable)
    {
      if (enumerable == null)
        return true;

      return !enumerable.Cast<object>().Any();
    }

    public static bool IsNullOrEmpty<T>(this IEnumerable<T> enumerable)
    {
      if (enumerable == null)
        return true;

      return !enumerable.Any();
    }

    public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start)
    {
      return Slice<T>(source, start, null, null);
    }

    public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start, int? stop)
    {
      return Slice<T>(source, start, stop, null);
    }

    public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int? start, int? stop, int? step)
    {
      if (source == null) throw new ArgumentNullException("source");

      if (step == 0) throw new ArgumentException("Step cannot be zero.", "step");

      IList<T> sourceCollection = source as IList<T>;
      if (sourceCollection == null)
        sourceCollection = new List<T>(source);

      // nothing to slice
      if (sourceCollection.Count == 0) yield break;

      // set defaults for null arguments
      int stepCount = step ?? 1;
      int startIndex = start ?? ((stepCount > 0) ? 0 : sourceCollection.Count - 1);
      int stopIndex = stop ?? ((stepCount > 0) ? sourceCollection.Count : -1);

      // start from the end of the list if start is negitive
      if (start < 0) startIndex = sourceCollection.Count + startIndex;

      // end from the start of the list if stop is negitive
      if (stop < 0) stopIndex = sourceCollection.Count + stopIndex;

      // ensure indexes keep within collection bounds
      startIndex = Math.Max(startIndex, (stepCount > 0) ? 0 : int.MinValue);
      startIndex = Math.Min(startIndex, (stepCount > 0) ? sourceCollection.Count : sourceCollection.Count - 1);
      stopIndex = Math.Max(stopIndex, -1);
      stopIndex = Math.Min(stopIndex, sourceCollection.Count);

      for (int i = startIndex; (stepCount > 0) ? i < stopIndex : i > stopIndex; i += stepCount)
      {
        yield return sourceCollection[i];
      }

      yield break;
    }

    public static IEnumerable<PositionEntry<T>> TrackPosition<T>(this IEnumerable<T> source)
    {
      return new PositionEnumerable<T>(source);
    }
  }
}
